using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Text;

namespace Developex.DatabaseWrapper
{
	/// <summary>
	/// db field
	/// </summary>
	public class Field<T> : IField
	{
		#region Fields

		/// <summary>
		/// field value
		/// </summary>
		protected T fieldValue;

		internal Row parent = null;
		internal State state = State.NotInitialized;

		#endregion

		#region Constructors

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name">field name</param>
		public Field(string name)
		{
			this.name = name;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		public Field(string name, FieldFlags flags)
		{
			this.flags = flags;
			this.name = name;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		public Field(string name, FieldFlags flags, T insertDefaultValue)
		{
			this.flags = flags;
			this.name = name;
			SetInsertDefaultValue(insertDefaultValue);
		}

		#endregion

		#region Field value state

		internal enum State
		{
			NotInitialized,
			Initialized,
			ManualSet
		}

		/// <summary>
		/// Is value initialized
		/// </summary>
		public bool IsInitialized()
		{
			return state == State.Initialized || state == State.ManualSet;
		}

		/// <summary>
		/// Is value manual set
		/// </summary>
		public bool IsManualSet()
		{
			return state == State.ManualSet;
		}

		#endregion

		#region Public virtual functions

		/// <summary>
		/// UpdatedOn,UpdatedBy and so on
		/// </summary>
		public virtual bool SetUpdateAutoValue(Connection conn)
		{
			return false;
		}

		/// <summary>
		/// CreatedOn,CreatedBy and so on
		/// </summary>
		public virtual bool SetCreateAutoValue(Connection conn)
		{
			return false;
		}

		/// <summary>
		/// Check value for correctness
		/// </summary>
		/// <returns></returns>
		public virtual bool CheckValue()
		{
			return true;
		}

		/// <summary>
		/// get/set owner of field
		/// </summary>
		public Row Parent
		{
			get { return parent; }
			set { parent = value; }
		}

		/// <summary>
		/// Compare two fields
		/// </summary>
		/// <param name="fieldToCompare"></param>
		/// <returns></returns>
		public virtual int CompareTo(IField fieldToCompare)
		{
			if (typeof(T) is IComparable)
			{
				return ((IComparable)Value).CompareTo(fieldToCompare.GetValue());
			}
			throw new NotImplementedException();
		}

		/// <summary>
		/// Expression used in SELECT query
		/// </summary>
		public virtual string GetSelectExpression()
		{
			return string.Format("[{0}].[{1}]", parent.JoinTableName, Name);
		}

		#endregion

		#region Public functions

		/// <summary>
		/// Set insert default value
		/// </summary>
		/// <param name="insertDefaultValue"></param>
		public void SetInsertDefaultValue(T insertDefaultValue)
		{
			fieldValue = insertDefaultValue;
			state = State.Initialized;
		}

		/// <summary>
		/// Get DbType
		/// </summary>
		/// <returns></returns>
		public DbType? GetDbType()
		{
			switch (typeof(T).Name)
			{
				case "Byte[]":
					return DbType.Binary;
				case "DateTime":
				case "DateTime?":
					return DbType.DateTime;
				case "Nullable`1":
					if (typeof(T).FullName.IndexOf("System.DateTime") != -1)
					{
						return DbType.DateTime;
					}
					break;
			}
			return null;
		}

		/// <summary>
		/// Get Type of Value
		/// </summary>
		/// <returns></returns>
		public Type GetValueType()
		{
			return typeof(T);
		}

		#endregion

		#region Properties

		private FieldFlags flags = FieldFlags.None;
		private string name;

		/// <summary>
		/// Get field flags
		/// </summary>
		public FieldFlags Flags
		{
			get { return flags; }
			set { flags = value; }
		}

		/// <summary>
		/// Get field name
		/// </summary>
		public string Name
		{
			get { return name; }
		}

		/// <summary>
		/// Get field name (with brackets)
		/// </summary>
		public string Name_br
		{
			get { return "[" + name + "]"; }
		}

		#endregion

		#region Get/Set value

		/// <summary>
		/// Get value
		/// </summary>
		/// <returns></returns>
		public object GetValue()
		{
			return Value;
		}

		/// <summary>
		/// Converts the class to its data type
		/// </summary>
		/// <param name="field">data</param>
		/// <returns>the native value</returns>
		public static implicit operator T(Field<T> field)
		{
			return field.Value;
		}

		/// <summary>
		/// get/set value
		/// </summary>
		public virtual T Value
		{
			get
			{
				Parent.AutoLoad();

				if (!IsInitialized())
					throw new InvalidOperationException(string.Format("{0}.{1}.Value: try to get not initialized value", parent.GetType().Name, Name));

				return fieldValue;
			}
			set
			{
				state = State.ManualSet;
				fieldValue = value;
			}
		}

		/// <summary>
		/// Explicit init value
		/// </summary>
		/// <param name="value"></param>
		public void SetInitValue(T value)
		{
			state = State.Initialized;
			fieldValue = value;
		}

		/// <summary>
		/// Set value (manual set)
		/// </summary>
		/// <param name="value"></param>
		public void SetValue(object value)
		{
			Set(value);
			state = State.ManualSet;
		}

		/// <summary>
		/// Init value (like when field is loaded from db)
		/// </summary>
		/// <param name="value"></param>
		public void InitValue(object value)
		{
			Set(value);
			state = State.Initialized;
		}

		/// <summary>
		/// Set value, check for DBNull
		/// </summary>
		/// <param name="value"></param>
		protected virtual void Set(object value)
		{
			if (value == DBNull.Value)
			{
				value = null;
			}
			else if (value != null)
			{
				string typeName = typeof(T).Name;

				if (typeName == "Nullable`1")
				{
					//
					typeName = Nullable.GetUnderlyingType(typeof(T)).Name;
				}

				switch (typeName)
				{
					case "Byte":
						value = Convert.ToByte(value);
						break;
					case "Int16":
						value = Convert.ToInt16(value);
						break;
					case "Int32":
						value = Convert.ToInt32(value);
						break;
					case "Int64":
						value = Convert.ToInt64(value);
						break;
					case "SByte":
						value = Convert.ToSByte(value);
						break;
					case "UInt16":
						value = Convert.ToUInt16(value);
						break;
					case "UInt32":
						value = Convert.ToUInt32(value);
						break;
					case "UInt64":
						value = Convert.ToUInt64(value);
						break;
					case "Decimal":
						value = Convert.ToDecimal(value);
						break;
					case "Double":
						value = Convert.ToDouble(value);
						break;
					case "Single":
						value = Convert.ToSingle(value);
						break;
					case "DateTime":
						value = Convert.ToDateTime(value);
						break;
					case "Boolean":
						value = Convert.ToBoolean(value);
						break;
					case "Guid":
						if (value is string)
							value = new Guid((string) value);
						break;
				}
			}
			fieldValue = (T)value;
		}

		#endregion

		#region IFieldBase Members

		/// <summary>
		/// Add field to select query
		/// </summary>
		/// <param name="qb">query builder</param>
		public virtual void AddToSelectQuery(QueryBuilder qb)
		{
			// add select
			string selectExpression = GetSelectExpression();
			if (!string.IsNullOrEmpty(selectExpression))
				qb.Select.Add(string.Format("({0}) AS [{1}{2}]", selectExpression, Parent.SelectPrefix, Name));

			// add join/prepare
			if (!qb.IsProcessed(this) && NeedToPrepare())
			{
				PrepareForSelectQuery(qb);
				qb.AddToProcessed(this);
			}
		}

		///<summary>
		///</summary>
		///<returns></returns>
		/// <remarks>overrided in ForeignField to avoid adding not used joins</remarks>
		public virtual bool NeedToPrepare()
		{
			return false;
		}

		/// <summary>
		/// Add joins and etc.
		/// </summary>
		/// <param name="qb">query builder</param>
		/// <remarks>don't forget to call qb.</remarks>
		public virtual void PrepareForSelectQuery(QueryBuilder qb)
		{
		}

		#endregion

		#region Implementation of ICloneable

		/// <summary>
		/// Clone
		/// </summary>
		/// <returns></returns>
		public virtual object Clone()
		{
			Field<T> field = CreateObjectForClone();
			Debug.Assert(GetType() == field.GetType());
			field.name = name;
			field.flags = flags;
			field.state = state;
			field.fieldValue = fieldValue;
			return field;
		}

		///<summary>
		/// Create object 
		///</summary>
		///<returns></returns>
		/// <remarks>
		/// Here should be only object creation, all data copy should be in overrided Clone function.
		/// Used only in base version of Clone function.
		/// Should be overrided for ICloneable interface.
		/// </remarks>
		protected virtual Field<T> CreateObjectForClone()
		{
			return new Field<T>(null);
		}

		#endregion
	}
}
